﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.ServiceModel;
using System.ComponentModel;
using System.ServiceModel.Description;
using System.Collections.ObjectModel;
using System.Threading;
using System.IO;
using System.Runtime.Remoting.Messaging;

namespace sync
{
    [ConnectionProviderInfo(typeof(PeerConnectionProviderInfo))]
    public class PeerConnectionProvider : IConnectionProvider
    {
        private readonly object sync = new object();

        AsyncOperation async;

        SendOrPostCallback onBringOnlineCompleted;

        private delegate void BringOnlineDelegate();

        PeerConnectionProviderInfo peerConnectionInfo;
        IPeerFileSyncChannel peerFileSyncChannel;
        PeerFileSync peerFileSync;
        ChannelFactory<IPeerFileSyncChannel> factory;

        private PeerConnectionProvider()
        {
            async = AsyncOperationManager.CreateOperation(null);
            this.InitalizeDelegates();
        }

        public PeerConnectionProvider(PeerConnectionProviderInfo info) :this()
        {
            peerConnectionInfo = info;
        }

        public ObservableCollection<KnowenAnnounceFileInfo> KnowenFiles
        {
            get
            {
                return this.peerFileSync.knowenFilesInfo;
            }
        }

        public ObservableCollection<SyncNode> KnowenNodes
        {
            get
            {
                return this.peerFileSync.knowenSyncNodes;
            }
        }

        public event EventHandler<AsyncCompletedEventArgs> BringOnlineCompleted;
        
        private bool isReady = false;
        public bool IsReady
        {
            get
            {
                return this.isReady;
            }
            set
            {
                if (this.isReady == value)
                    return;
                this.isReady = value;
                this.NotifyPropertyChanged("IsReady");
            }
        }
        
        public RepositoryInfo RepositoryInfo { get; set; }
        
        public void Create()
        {
            this.RepositoryInfo.ConnectionProviderType = this.GetType().ToString();
            this.RepositoryInfo.ConnectionProviderConfig = peerConnectionInfo;
        }
        
        public void Init()
        {
            peerFileSync = new PeerFileSync();
            InstanceContext mesh = new InstanceContext(peerFileSync);
            NetPeerTcpBinding peerBinding = new NetPeerTcpBinding();
           
            EndpointAddress endPoint =  new EndpointAddress("net.p2p://FileSync/" + this.RepositoryInfo.Id.ToString());
            
            factory = new DuplexChannelFactory<IPeerFileSyncChannel>(mesh, peerBinding,endPoint);
            factory.Credentials.Peer.MeshPassword = this.peerConnectionInfo.MeshPassword;
        }

        private void InitalizeDelegates()
        {
            this.onBringOnlineCompleted = new SendOrPostCallback(this.OnBringOnlineCompleted);
        }

        private void OnBringOnlineCompleted(object e)
        {
            if (this.BringOnlineCompleted != null)
                this.BringOnlineCompleted.Invoke(this, e as AsyncCompletedEventArgs);
        }
        
        public void BringOnline()
        {
            
            // Need to make multi threaded
            // Need to add encryption

            BringOnlineDelegate worker = new BringOnlineDelegate(this.BringOnlineThread);
            AsyncCallback callback = new AsyncCallback(this.BringOnlineThreadCallback);
            lock (this.sync)
            {
                if (this.IsReady)
                {
                    throw new InvalidOperationException("The control is currently Online.");
                }
                worker.BeginInvoke(callback, this.async);
            }
        }
        private void BringOnlineThread()
        {
            peerFileSyncChannel = factory.CreateChannel();
            peerFileSyncChannel.Logon(this.RepositoryInfo.ClientId.ToString());
        }

        private void BringOnlineThreadCallback(IAsyncResult ar)
        {
            BringOnlineDelegate worker = (BringOnlineDelegate)((AsyncResult)ar).AsyncDelegate;
            AsyncOperation async = (AsyncOperation)ar.AsyncState;

            worker.EndInvoke(ar);
            lock (this.sync)
            {
                this.IsReady = true;
            }

            AsyncCompletedEventArgs args = new AsyncCompletedEventArgs(null, false, null);

            this.async.PostOperationCompleted(this.onBringOnlineCompleted, args);
        }
        public void AnounceFileList(List<AnnounceFileInfo> filesToAnnounce)
        {
            peerFileSyncChannel.AnnounceFileList(this.RepositoryInfo.ClientId.ToString(), filesToAnnounce);
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        public Stream GetStreamForFile(KnowenAnnounceFileInfo knowenAnnouceFileInfo)
        {
            // Something like this perhaps
            // use PNRP to resolve by clientID
            //http://stackoverflow.com/questions/3202594/stream-to-file-while-returning-a-wcf-stream
            // Underlying connection must be encrypted
            throw new NotImplementedException();
        }
        
    }
}
